package org.aliyunoss.service.impl;


import cn.hutool.core.io.FileTypeUtil;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import org.aliyunoss.config.AliyunOssConfig;
import org.aliyunoss.service.OssService;
import org.aliyunoss.utils.MyFileUtils;
import org.aliyunoss.vo.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;


@Service
public class OSSImpl implements OssService {
    @Autowired
    AliyunOssConfig aliyunOssConfig;
    @Autowired
    private ThreadPoolExecutor executor;
    @Autowired
    private OSS ossClient;
    private Logger logger = LoggerFactory.getLogger(OSSImpl.class);

    //for循环遍历上传普通文件，不加线程池
    @Override
    public List<String> uploadImages(List<MultipartFile> multipartFile) {
        this.ossClient = AliyunOssConfig.createOss(aliyunOssConfig);
        /*
        耗时记录输出
        */
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String beginTime = sdf.format(date);
        long l1 = System.currentTimeMillis();
        Map map = new ConcurrentHashMap();

        long test = l1;
        //String beginTime = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
        logger.info("fileUploadZone-开始上传时间：" + beginTime + "时间戳：" + l1);

        //保存上传后返回的云端文件URLs
        List<String> responseUrls = Collections.synchronizedList(new ArrayList<>());
        //设置url过期时间 上传时间后五年后失效
        Date expiration = new Date(System.currentTimeMillis() + 5 * 365 * 24 * 3600 * 1000);
        String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

        try {
            for (MultipartFile file : multipartFile) {
                String originalFilename = file.getOriginalFilename();
                String cloudFileName = new StringBuilder()
                        .append(UUID.randomUUID().toString())
                        .append(MyFileUtils.getExtensionName(originalFilename))
                        .toString();
                //阿里云OSS bucket下存储位置
                String cloudPath = dir + "/" + cloudFileName;
                //设置ContentType，使得返回的url可以在网页中预览（仅有少部分格式支持在线预览）  默认不设置或不支持在线预览的，返回的url是下载附件，而不是预览(可以从前端传个参数来判断是在线预览还是下载)

                ObjectMetadata objectMetadata = new ObjectMetadata();
                //判断文件类型(获取扩展名方式)
                //objectMetadata.setContentType(MyFileUtils.getContentType(StringUtils.substringAfterLast(originalFilename, ".")));
                //判断文件类型，通过hutool工具类，本质是根据件流头部16进制字符串进行判断
                objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file))));

                // 设置URL过期时间为1小时。
                InputStream multipartFileInputStream = file.getInputStream();
                //PutObjectRequest putObjectRequest = new PutObjectRequest(aliyunOssConfig.getBucket(), cloudPath, multipartFileInputStream);

                ossClient.putObject(aliyunOssConfig.getBucket(), cloudPath, multipartFileInputStream, objectMetadata);
                //ossClient.generatePresignedUrl()
                String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString();
                //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息
                url = url.substring(0, url.indexOf("?"));

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("fileName", cloudFileName);
                jsonObject.put("url", url);
                map.put("file", jsonObject);
                responseUrls.add(url);

                /*
                耗时记录输出
                 */
                Date dateBlock = new Date();
                SimpleDateFormat sdfBlock = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String blockBeginTime = sdf.format(date);
                long block = System.currentTimeMillis();
                logger.info("文件" + file + "上传时间：" + blockBeginTime + "当前时间戳：" + block + ",耗时：" + (block - test));
                test = block;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            ossClient.shutdown();
        }
        Date date2 = new Date();
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
        String endTime = sdf2.format(date2);
        long l2 = System.currentTimeMillis();

        logger.info("uploadImages-结束上传时间：" + endTime + " 总耗时：" + (l2 - l1) + "ms");
        return responseUrls;
    }

    //分片上传 单个文件
    @Override
    public Result fileUploadZone(MultipartFile file) {
        this.ossClient = AliyunOssConfig.createOss(aliyunOssConfig);
        try {
            /*
             耗时记录输出
            */
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String beginTime = sdf.format(date);
            long l1 = System.currentTimeMillis();
            long test = l1;
            //String beginTime = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();

            logger.info("fileUploadZone-开始上传时间：" + beginTime + "时间戳：" + l1);

            //设置url过期时间 上传时间后一周内失效
            Date expiration = new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000);
            //获取文件的原始名字
            String originalfileName = file.getOriginalFilename();
            //文件后缀
            String suffix = originalfileName.substring(originalfileName.lastIndexOf(".") + 1);
            //重新命名文件，文件夹要是改动，app记录删除的地方一并改动
            String pack = "file/";
            String fileName = "file_" + System.currentTimeMillis() + "." + suffix;
            String cloudPath = pack + fileName;

            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath);
            // 如果需要在初始化分片时设置文件存储类型，请参考以下示例代码。
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file))));
            // ObjectMetadata metadata = new ObjectMetadata();
            //objectMetadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            //String files = URLEncoder.encode(cloudPath, "UTF-8");
            //objectMetadata.setHeader("Content-Disposition", "filename*=utf-8''" + files);
            request.setObjectMetadata(objectMetadata);
            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId，它是分片上传事件的唯一标识，可以根据这个uploadId发起相关的操作，如取消分片上传、查询分片上传等。
            String uploadId = upresult.getUploadId();
            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags = new ArrayList<PartETag>();
            // 计算文件有多少个分片。
            // 2MB
            final long partSize = 2 * 1024 * 1024L;
            long fileLength = file.getSize();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                // 跳过已经上传的分片。
                InputStream instream = file.getInputStream();
                instream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(aliyunOssConfig.getBucket());
                uploadPartRequest.setKey(cloudPath);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
                uploadPartRequest.setPartNumber(i + 1);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());

                /*
                耗时记录输出
                 */
                Date dateBlock = new Date();
                SimpleDateFormat sdfBlock = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String blockBeginTime = sdf.format(date);
                long block = System.currentTimeMillis();
                logger.info("第" + i + "块block上传时间：" + blockBeginTime + "当前时间戳：" + block + ",耗时：" + (block - test));
                test = block;
            }
            /**
             * 创建CompleteMultipartUploadRequest对象。
             * 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。
             * 当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
             */

            //设置ContentType，使得返回的url可以在网页中预览

            CompleteMultipartUploadRequest uploadRequest = new CompleteMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath, uploadId, partETags);
            // 在完成文件上传的同时设置文件访问权限。
            uploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
            // 完成上传。
            ossClient.completeMultipartUpload(uploadRequest);
            String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString();
            //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息
            url = url.substring(0, url.indexOf("?"));
            // 关闭OSSClient。
            ossClient.shutdown();

            Date date2 = new Date();
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
            String endTime = sdf2.format(date2);
            long l2 = System.currentTimeMillis();
            logger.info("fileUploadZone-结束上传时间：" + endTime + " 总耗时：" + (l2 - l1) + "ms");
            Map<String, Object> map = new HashMap<>();
            map.put("url", url);
            map.put("name", fileName);
            return Result.success(map);

        } catch (Exception e) {
            e.printStackTrace();
            ossClient.shutdown();
            //logger.error(e.getMessage());
            return Result.fail(111111, "操作失败！");
        }
    }

    //线程池上传多个文件
    @Override
    public List<String> upload(List<MultipartFile> multipartFile) {


        String beginTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        long l1 = System.currentTimeMillis();
        logger.info("upload-开始上传时间：" + beginTime + "时间戳：" + l1);
        // 图片上传至阿里云OSS，设置返回路径集合
        List<String> list = new ArrayList<>();
        //线程安全
        List<String> responseUrls = Collections.synchronizedList(new ArrayList<String>());

        // 用户上传文件时指定的前缀，即存放在以时间命名的文件夹内
        String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        //定义List类型的submit，用来接收上传文件的url
        Future<List> submit = null;

        int coreThreads = Runtime.getRuntime().availableProcessors();
        logger.info("当前计算机核心线程数：" + coreThreads);
        //创建线程池 核心线程数：当前当前计算机核心线程数  同时容纳最大线程：5*当前当前计算机核心线程数  非核心空闲线程存活时间：30 存活时间单位：毫秒（1/1000s） 任务队列:当前当前计算机核心线程数*10 拒绝策略：默认
        ThreadPoolExecutor threadPoolExecutor = new org.apache.tomcat.util.threads.ThreadPoolExecutor
                (coreThreads, coreThreads * 5, 30L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(coreThreads * 10));

        CountDownLatch latch = new CountDownLatch(multipartFile.size());
        for (MultipartFile file : multipartFile) {
            // 多线程上传图片 使用submit方法 为了返回上传文件的url
            submit = threadPoolExecutor.submit((Callable<List>) () -> {
                        ossClient = AliyunOssConfig.createOss(aliyunOssConfig);
                        String originalFilename = file.getOriginalFilename();
                        // 设置上传到云存储的文件名，规则为"当前时间-UUID.源文件后缀名"
                        String cloudFileName = new StringBuilder()
                                .append(UUID.randomUUID().toString())
                                .append(MyFileUtils.getExtensionName(originalFilename))
                                .toString();
                        // 设置上传到云存储的路径
                        String cloudPath = dir + "/" + cloudFileName;
                        //设置ContentType，使得返回的url可以在网页中预览
                        ObjectMetadata objectMetadata = new ObjectMetadata();
                        objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file))));
                        // 图片上传
                        try {
                            ossClient.putObject(aliyunOssConfig.getBucket(), cloudPath, file.getInputStream(), objectMetadata);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }

                        //设置线程名称
                        Thread.currentThread().setName("线程" + originalFilename);
                        //设置expiration
                        Date expiration = new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000);
                        String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString();
                        url = url.substring(0, url.indexOf("?"));
                        responseUrls.add(url);
                        return responseUrls;
            }
            );
        }

        Date date2 = new Date();
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
        String endTime = sdf2.format(date2);
        long l2 = System.currentTimeMillis();
        logger.info("upload-结束上传时间：" + endTime + " 总耗时：" + (l2 - l1) + "ms");
        //关闭线程池
        threadPoolExecutor.shutdown();
        try {
            return submit.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public Result filesUploadZone(List<MultipartFile> multipartFile) {

        String beginTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        long l1 = System.currentTimeMillis();
        long test = l1;
        logger.info("upload-开始上传时间：" + beginTime + "时间戳：" + l1);
        // 图片上传至阿里云OSS，设置返回路径集合
        List<JSONObject> responseUrls = new ArrayList<>();
        // 用户上传文件时指定的前缀，即存放在以时间命名的文件夹内
        String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        //CompletableFuture<PutObjectResult> future = null;
        Map<String, Object> map = new HashMap<>();
        CompletableFuture<Result> future = null;
        for (MultipartFile file : multipartFile) {
            //获取文件的原始名字
            String originalfileName = file.getOriginalFilename();

            //文件后缀
            String suffix = originalfileName.substring(originalfileName.lastIndexOf(".") + 1);
            //重新命名文件，文件夹要是改动，app记录删除的地方一并改动
            String pack = "file/";
            String fileName = "file_" + System.currentTimeMillis() + "." + suffix;
            String cloudPath = pack + fileName;

            // 线程池异步上传图片
            future = CompletableFuture.supplyAsync(() -> {
                try {
                    this.ossClient = AliyunOssConfig.createOss(aliyunOssConfig);
                    //OSS ossClient = new OSSClientBuilder().build(aliyunOssConfig.getEndpoint(), aliyunOssConfig.getAccessKey(), aliyunOssConfig.getSecretKey());
                    // 创建InitiateMultipartUploadRequest对象。
                    InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath);
                    // 如果需要在初始化分片时设置文件存储类型，请参考以下示例代码。
                    ObjectMetadata objectMetadata = new ObjectMetadata();
                    //objectMetadata.setContentType(MyFileUtils.getContentType(StringUtils.substringAfterLast(originalfileName, ".")));
                    objectMetadata.setContentType(MyFileUtils.getContentType(FileTypeUtil.getType(MyFileUtils.multipartFileToFile(file))));
                    // ObjectMetadata metadata = new ObjectMetadata();
                    //objectMetadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
                    //String files = URLEncoder.encode(cloudPath, "UTF-8");
                    //objectMetadata.setHeader("Content-Disposition", "filename*=utf-8''" + files);
                    request.setObjectMetadata(objectMetadata);
                    // 初始化分片。
                    InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
                    // 返回uploadId，它是分片上传事件的唯一标识，可以根据这个uploadId发起相关的操作，如取消分片上传、查询分片上传等。
                    String uploadId = upresult.getUploadId();
                    // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
                    List<PartETag> partETags = new ArrayList<PartETag>();
                    // 计算文件有多少个分片。
                    // 2MB
                    final long partSize = 2 * 1024 * 1024L;
                    long fileLength = file.getSize();
                    int partCount = (int) (fileLength / partSize);
                    if (fileLength % partSize != 0) {
                        partCount++;
                    }

                    // 遍历分片上传。
                    for (int i = 0; i < partCount; i++) {
                        long startPos = i * partSize;
                        long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                        // 跳过已经上传的分片。
                        InputStream instream = file.getInputStream();
                        instream.skip(startPos);
                        UploadPartRequest uploadPartRequest = new UploadPartRequest();
                        uploadPartRequest.setBucketName(aliyunOssConfig.getBucket());
                        uploadPartRequest.setKey(cloudPath);
                        uploadPartRequest.setUploadId(uploadId);
                        uploadPartRequest.setInputStream(instream);
                        // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                        uploadPartRequest.setPartSize(curPartSize);
                        // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
                        uploadPartRequest.setPartNumber(i + 1);
                        // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
                        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                        // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                        partETags.add(uploadPartResult.getPartETag());
                    }
                    /**
                     * 创建CompleteMultipartUploadRequest对象。
                     * 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。
                     * 当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
                     */

                    //设置ContentType，使得返回的url可以在网页中预览

                    CompleteMultipartUploadRequest uploadRequest = new CompleteMultipartUploadRequest(aliyunOssConfig.getBucket(), cloudPath, uploadId, partETags);
                    // 在完成文件上传的同时设置文件访问权限。
                    uploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
                    // 完成上传。
                    ossClient.completeMultipartUpload(uploadRequest);

                    Date expiration = new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000);
                    String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString();
                    //去掉url尾部的Expires信息、OSSAccessKeyId信息以及Signature信息
                    url = url.substring(0, url.indexOf("?"));

                    Date date2 = new Date();
                    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
                    String endTime = sdf2.format(date2);
                    long l2 = System.currentTimeMillis();
                    logger.info("filesUploadZone-结束上传时间：" + endTime + " 总耗时：" + (l2 - l1) + "ms");
                    //responseUrls.add(url);
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("url", url);
                    jsonObject.put("name", fileName);
                    responseUrls.add(jsonObject);
                    return Result.success(responseUrls);
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            }, executor)
            //        .whenComplete((res, exception) -> {
            //
            //    // 判断是否正确返回，将图片链接添加到集合中
            //    if (!ObjectUtils.isEmpty(res)) {
            //        //log.info("处理照片{}", aliyunOssConfig.getMarketHost() + "/" + cloudPath);
            //        Date expiration = new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000);
            //        String url = ossClient.generatePresignedUrl(aliyunOssConfig.getBucket(), cloudPath, expiration).toString();
            //        String substring = url.substring(0, url.indexOf("?"));
            //        JSONObject jsonObject = new JSONObject();
            //        jsonObject.put("url", substring);
            //        //jsonObject.put("name", fileName);
            //        responseUrls.add(jsonObject);
            //    }
            //})
            ;
            // 等待线程执行完毕
            if (future != null) {
                ossClient.shutdown();
                future.join();
            }
        }
        return Result.success(responseUrls);
    }
}
